﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using Lung_Sounds;
using ZedGraph;
using EricOulashin;
using System.Threading;
using System.IO;

namespace Lung_Sounds
{
    public partial class Form1 : Form
    {
        private WaveFormat _wavFormat;
        private WaveInRecorder _wavRecorder;
        private byte[] _recorderBuffer;
        private short[] _waveBuffer;
        private byte[] _playerBuffer;
        private LineItem _fftCurve;
        private LineItem _waveCurve;
        private bool _isPaused;
        private WAVFile _wavSaver;
        private WAVFile _wavOpener;
        private Thread _wavPlayThread;
        private string _wavLocation;
        private SigGen _signalGen;

        private bool _processGraphs = false;


        private SoundFilter _filter;
        private SoundFilter.FilterState _highFilterState;
        private SoundFilter.FilterState _lowFilterState;
        private SoundFilter.FilterState _bandStopState;

        private bool _enableFiltering;
        private WaveOutPlayer _player;

        private FifoStream _playerStream;
        private BinaryWriter _playerWriter;

        private int _sampleRate;
        private int _bitsPerSample;
        private int _samplesPerWindow;
        private double _maxAmplitude;
        private double _amplification = 1;

        private DateTime _lastCaptureTime;
        private int _lostTime;

        private int _limitPercent = 100;

        // The size of the audio in buffer = this * WindowsSize
        private int _bufferSizeMultiplier;


        public double WindowSize
        {
            get
            {
                return (1.00 / _sampleRate) * _samplesPerWindow; 
            }
        }

        public Form1()
        {
            _sampleRate = 44100;
            _bitsPerSample = 16;
            _samplesPerWindow = (int)Math.Pow(2, 12);
            _bufferSizeMultiplier = 2;

            _signalGen = new SigGen();
            _signalGen.SetSamples(_samplesPerWindow);
            _signalGen.SetFrequency(0);
            _signalGen.SetAmplitude(5000);

            _filter = new SoundFilter();
            _filter.FilterRanges.Add(new double[2] { 0, 60 });
            _filter.FilterRanges.Add(new double[2] { 5000, 80000 });

            _playerStream = new FifoStream();
            _playerWriter = new BinaryWriter(_playerStream);

            InitializeComponent();
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            if (WaveNative.waveInGetNumDevs() == 0)
            {
                MessageBox.Show("No audio input devices detected!");
                Close();
            }
            else
            {
                microphoneToolStripMenuItem_Click(this, new EventArgs());
                fftGraph.GraphPane.Title.Text = "FFT";
                waveGraph.GraphPane.Title.Text = "Wave";
            }

            splitContainer1.Panel1Collapsed = true;
        }

        private void StartPlayer(int bufferSize)
        {
            _wavFormat = new WaveFormat(_sampleRate, _bitsPerSample, 1);
            _player = new WaveOutPlayer(-1, _wavFormat, bufferSize, 2, new BufferFillEventHandler(BufferFill));
        }

        /// <summary>
        /// Starts capturing and processing sound
        /// </summary>
        private void Start()
        {
            try
            {
                // Start recording at sample rate 44100 with 16 bits per sample
                // DataArrive gets run every sample
                int bytesPerSample = _bitsPerSample / 8;
                _filter.WindowSize = WindowSize;

                StartPlayer(_samplesPerWindow * bytesPerSample * _bufferSizeMultiplier);
                _wavRecorder = new WaveInRecorder(-1, _wavFormat, _samplesPerWindow * bytesPerSample * _bufferSizeMultiplier, 3, new BufferDoneEventHandler(DataArrived));
                spectrogramPane1.WindowSamples = _samplesPerWindow;
            }
            catch (Exception ex)
            {
                MessageBox.Show(string.Format("Error while starting recording: {0}", ex));
            }
        }

        /// <summary>
        /// Gets called by the WaveOutPlayer each time the output buffer needs to be filled
        /// </summary>
        /// <param name="data">Input buffer data (array of samples of size _bitsPerSample)</param>
        /// <param name="size">Size of the buffer</param>
        private void BufferFill(IntPtr data, int size)
        {
            
                if (_playerBuffer == null || _playerBuffer.Length < size)
                    _playerBuffer = new byte[size];
                if (_playerStream.Length >= size)
                    _playerStream.Read(_playerBuffer, 0, size);
                else
                    for (int i = 0; i < _playerBuffer.Length; i++)
                        _playerBuffer[i] = 0;
                System.Runtime.InteropServices.Marshal.Copy(_playerBuffer, 0, data, size);
            
        }

        /// <summary>
        /// Stops all sound processing and capture
        /// </summary>
        private void Stop(bool stopPlayer = true)
        {
            if (_wavRecorder != null)
            {
                _wavRecorder.Dispose();
                _wavFormat = null;
                _wavRecorder = null;
                if (stopPlayer)
                {
                    _player.Dispose();
                    _player = null;
                }
            }
        }

        /// <summary>
        /// Clears the spectrogram
        /// </summary>
        public void Reset()
        {
            spectrogramPane1.Clear();
        }

        /// <summary>
        /// Clears and re-fills the wave graph
        /// </summary>
        /// <param name="wave">Array of wave amplitudes for the current window</param>
        /// <param name="startTime">Time to use as x=0</param>
        private void GenerateWaveGraph(double[] wave, double startTime = 0)
        {
            if (_processGraphs)
            {
                // Wave Graph
                if (_waveCurve == null)
                {
                    GraphPane wavePane = waveGraph.GraphPane;
                    PointPairList waveList = new PointPairList();

                    wavePane.XAxis.Title.Text = "Time (sec)";
                    wavePane.YAxis.Title.Text = "Amplitude (dB)";

                    _waveCurve = wavePane.AddCurve("Wave",
                        waveList, Color.Red, SymbolType.None);
                }

                _waveCurve.Clear();

                int x = 0;
                foreach (double waveValue in wave)
                {
                    _waveCurve.AddPoint((double)x * (1d / 44100d) + startTime, waveValue);
                    x++;
                }

                waveGraph.AxisChange();
                waveGraph.Invalidate();
            }
        }

        /// <summary>
        /// Clears, and re-fills the fft graph
        /// </summary>
        /// <param name="fft">List of amplitudes for each fft bin</param>
        private void GenerateFFTGraph(double[] fft)
        {
            if (_processGraphs)
            {
                // FFT Graph
                if (_fftCurve == null)
                {
                    GraphPane fftPane = fftGraph.GraphPane;
                    PointPairList fftList = new PointPairList();

                    fftPane.XAxis.Title.Text = "Frequency (Hz)";
                    fftPane.YAxis.Title.Text = "Amplitude (dB)";

                    _fftCurve = fftPane.AddCurve("FFT",
                        fftList, Color.Blue, SymbolType.None);
                }

                _fftCurve.Clear();

                int x = 0;
                foreach (double fftValue in fft)
                {
                    _fftCurve.AddPoint(x * (1.00 / WindowSize), fftValue);
                    x++;

                }

                fftGraph.AxisChange();
                fftGraph.Invalidate();
            }
        }

        /// <summary>
        /// Called by WaveInRecorder each time it's internal buffer is filled
        /// </summary>
        /// <param name="data">Data contained in the buffer (array of samples of size _bitsPerSample)</param>
        /// <param name="size">Size of the buffer</param>
        private void DataArrived(IntPtr data, int size)
        {
            if (_recorderBuffer == null || _recorderBuffer.Length != size)
                _recorderBuffer = new byte[size];

            if (_waveBuffer == null || _waveBuffer.Length != size)
                _waveBuffer = new short[size / (_bitsPerSample / 8)];

            System.Runtime.InteropServices.Marshal.Copy(data, _waveBuffer, 0, _waveBuffer.Length);

            // Save wav frame if recording enabled
            if (_wavSaver != null)
            {
                foreach (double sample in _waveBuffer)
                {
                    _wavSaver.AddSample_16bit((short)sample);
                }
            }

            if (_lastCaptureTime != null)
            {
                int msSinceLastCapture = (int)(DateTime.Now - _lastCaptureTime).TotalMilliseconds;
                if (msSinceLastCapture > WindowSize * _bufferSizeMultiplier * 1000 + 30)
                {
                    _lostTime += (int)(msSinceLastCapture - WindowSize * _bufferSizeMultiplier * 1000 + 30);
                    UpdateLostTimeText(_lostTime.ToString());
                }
            }

            _lastCaptureTime = DateTime.Now;

            if (_enableFiltering)
            {
                _waveBuffer = _filter.FilterWaveLowButter(_waveBuffer, ref _lowFilterState);
                _waveBuffer = _filter.FilterWaveHighPass(_waveBuffer, ref _highFilterState);
                _waveBuffer = _filter.FilterWaveBandStop3850_3992(_waveBuffer, ref _bandStopState);
            }

            for (int i = 0; i < _bufferSizeMultiplier; i++)
            {
                DataArrived(i);
            }
            spectrogramPane1.UpdateGraph();
        }

        private void DataArrived(int bufferWindowId)
        {
            int startId = _samplesPerWindow * bufferWindowId;

            // Get double array from byte array
            double[] wave = new double[(_recorderBuffer.Length / 2) / _bufferSizeMultiplier];

            Array.Copy(_waveBuffer, startId, wave, 0, wave.Length);

            for (int i = 0; i < wave.Length; i += 1)
            {
                if (Math.Abs(wave[i]) > _maxAmplitude) _maxAmplitude = Math.Abs(wave[i]);
                wave[i] *= _amplification;
            }


            if (_signalGen.GetFrequency() > 0)
            {
                double[] genSignal = _signalGen.GenerateSignal();

                for (int i = 0; i < wave.Length; i++)
                {
                    wave[i] += genSignal[i];
                }
            }

            for (int i = 0; i < wave.Length; i++)
            {
                _playerWriter.Write((Int16)(wave[i]));
            }


            // If graphs are not paused, update them
            FFTResult result = PerformFFT(wave);

            UpdateFreqText(String.Format("Max Freq: {0} Hz", result.MaxFrequency));

            if (!_isPaused && bufferWindowId == _bufferSizeMultiplier - 1)
            {
                UpdateGraphs(wave, result.FFT, true);
            }
            else
            {
                spectrogramPane1.Windows.Add(result.FFT);
            }
        }

        /// <summary>
        /// Updates the graphs
        /// </summary>
        /// <param name="wave"></param>
        /// <param name="fft"></param>
        /// <param name="skipSpect"></param>
        /// <param name="startTime"></param>
        private void UpdateGraphs(double[] wave, double[] fft, bool skipSpect = false, double startTime = 0)
        {
            
            GenerateWaveGraph(wave, startTime);
            if (!skipSpect) spectrogramPane1.Windows.Add(fft);
            GenerateFFTGraph(fft);
        }

        private delegate void UpdateTextDelegate(string text);
        private void UpdateLostTimeText(string text)
        {
            if (this.InvokeRequired)
            {
                this.Invoke(new UpdateTextDelegate(UpdateLostTimeText), text);
            }
            else
            {
                txtLostSamples.Text = string.Format("Lost ms: {0}", text);
            }
        }
        private void UpdateFreqText(string text)
        {
            if (this.InvokeRequired)
            {
                this.Invoke(new UpdateTextDelegate(UpdateFreqText), text);
            }
            else
            {
                //txtFrequency.Text = string.Format("Gen Freq: {1} Hz, {0}", text, trackBarFreq.Value);
            }
        }

        private void Form1_Resize(object sender, EventArgs e)
        {
            if (_wavRecorder != null)
            {
                Stop();
                Start();
            }
        }

        private void buttonPause_CheckedChanged(object sender, EventArgs e)
        {
            if (buttonPause.Checked)
            {
                _isPaused = true;
            }
            else
            {
                _isPaused = false;
            }
        }

        private void buttonRecord_CheckedChanged(object sender, EventArgs e)
        {
            try
            {
                if (buttonRecord.Checked)
                {
                    _wavSaver = new WAVFile();
                    _wavLocation = string.Format(@"{0:yyyy-MM-dd_hh-mm-ss-tt}.wav", DateTime.Now);
                    _wavSaver.Create(_wavLocation, false, 44100, 16);
                }
                else
                {
                    _wavSaver.Close();
                    _wavSaver = null;

                    SaveFileDialog saveDialog = new SaveFileDialog();
                    saveDialog.InitialDirectory = @"C:\Users\mrmt32\Dropbox\MPHYS Project\Breath Sounds\wav files\";
                    saveDialog.Filter = "Wave Files (*.WAV)|*.WAV";
                    saveDialog.FileName = string.Format(@"{0:yyyy-MM-dd_hh-mm-ss-tt}.wav", DateTime.Now);

                    if (saveDialog.ShowDialog() == System.Windows.Forms.DialogResult.OK)
                    {
                        WAVFile inFile = new WAVFile();
                        inFile.Open(_wavLocation, WAVFile.WAVFileMode.READ);

                        WAVFile outFile = new WAVFile();
                        outFile.Create(saveDialog.FileName, false, 44100, 16);

                        SoundFilter.FilterState lowState = null;
                        SoundFilter.FilterState highState = null;
                        SoundFilter.FilterState stopState = null;

                        while (inFile.NumSamplesRemaining > 0)
                        {
                            short[] sample = new short[] { inFile.GetNextSample_16bit() };

                            sample = _filter.FilterWaveHighPass(sample, ref highState);
                            sample = _filter.FilterWaveLowButter(sample, ref lowState);
                            sample = _filter.FilterWaveBandStop3850_3992(sample, ref stopState);

                            outFile.AddSample_16bit(sample[0]);
                        }

                        outFile.Close();
                        inFile.Close();
                    }
                }
            }
            catch (Exception ex)
            {
                MessageBox.Show(string.Format("Error during record: \n{0}", ex), "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
        }

        private void Form1_FormClosing(object sender, FormClosingEventArgs e)
        {
            Stop();
        }

        private void buttonSaveSpec_Click(object sender, EventArgs e)
        {
            System.Drawing.Imaging.EncoderParameters encParams = new System.Drawing.Imaging.EncoderParameters();
            SaveFileDialog saveDialog = new SaveFileDialog();
            saveDialog.InitialDirectory = @"C:\Users\mrmt32\Dropbox\MPHYS Project\Breath Sounds\wav files\";
            saveDialog.Filter = "PNG Images (*.PNG)|*.PNG";
            saveDialog.FileName = string.Format(@"{0:yyyy-MM-dd_hh-mm-ss-tt}.png", DateTime.Now);

            if (saveDialog.ShowDialog() == System.Windows.Forms.DialogResult.OK)
            {
                spectrogramPane1.Image.Save(saveDialog.FileName, System.Drawing.Imaging.ImageFormat.Png);     
            }
            
        }

        private void microphoneToolStripMenuItem_Click(object sender, EventArgs e)
        {
            microphoneToolStripMenuItem.Checked = true;
            openWaveFileToolStripMenuItem.Checked = false;
            trackBarWav.Enabled = false;

            if (_wavRecorder != null)
            {
                Stop();
            }

            if (_wavOpener != null)
            {
                _wavOpener.Close();
            }

            if (_wavSaver != null)
            {
                _wavSaver.Close();
            }


            Reset();
            Start();
        }

        private void openWaveFileToolStripMenuItem_Click(object sender, EventArgs e)
        {
            OpenFileDialog openDialog = new OpenFileDialog();

            openDialog.InitialDirectory = @"C:\Users\mrmt32\Dropbox\MPHYS Project\Breath Sounds\wav files\";
            openDialog.Filter = "Wave Files (*.WAV)|*.WAV";

            if (openDialog.ShowDialog() == DialogResult.OK)
            {
                microphoneToolStripMenuItem.Checked = false;
                openWaveFileToolStripMenuItem.Checked = true;

                // If we are currently recording from a microphone then stop!
                if (_wavRecorder != null)
                {
                    Stop();
                    Reset();
                }

                if (_wavOpener != null)
                {
                    _wavOpener.Close();
                }

                if (_wavSaver != null)
                {
                    _wavSaver.Close();
                }

                try
                {
                    _wavOpener = new WAVFile();
                    _wavOpener.Open(openDialog.FileName, WAVFile.WAVFileMode.READ);

                    this.Text = string.Format("Lung Sounds - {0}", openDialog.FileName);

                    //_wavOpenThread = new Thread(new ThreadStart(WavReaderThread));
                    //_wavOpenThread.Start();
                    SpectrogramFromWav();

                    trackBarWav.Enabled = true;
                }
                catch(Exception ex)
                {
                    MessageBox.Show(string.Format("Error opening wave file\n{0}", ex), "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
                }
            }
        }

        public struct FFTResult
        {
            public double MaxAmplitude { get; set; }
            public double MaxFrequency { get; set; }
            public double[] FFT { get; set; }
        }

        /// <summary>
        /// Perfoms an FFT on the wave form
        /// </summary>
        /// <param name="wave"></param>
        /// <returns></returns>
        private FFTResult PerformFFT(double[] wave)
        {
            AForge.Math.Complex[] fftComp = new AForge.Math.Complex[wave.Length];

            for (int i = 0; i < wave.Length; i++)
            {
                fftComp[i].Re = wave[i];
            }

            fftComp = Hann(fftComp);

            AForge.Math.FourierTransform.FFT(fftComp, AForge.Math.FourierTransform.Direction.Forward);

            double[] fft = new double[fftComp.Length / 2];


            double maxMag = 0;

            for (int i = 0; i < fftComp.Length / 2; i++)
            {
                if (fftComp[i].Magnitude > maxMag)
                {
                    maxMag = fftComp[i].Magnitude;
                }
            }

            for (int i = 0; i < fft.Length; i++)
            {
                // Trim quiet sounds
                if (fftComp[i].Magnitude < maxMag * ((100 - (double)_limitPercent) / 100))
                {
                    fftComp[i] = new AForge.Math.Complex(0, 0);
                }
            }

            double maxDb = 0;
            double maxFreq = 0;
            for (int i = 0; i < fftComp.Length / 2; i++)
            {
                double freq = i * (1.00 / WindowSize);
                fft[i] = 10.0 * Math.Log10(fftComp[i].Magnitude);

                if (fft[i] > maxDb)
                {
                    maxDb = fft[i];
                    maxFreq = i * (1.00 / WindowSize);
                }
            }

            FFTResult result = new FFTResult();

            result.FFT = fft;
            result.MaxAmplitude = maxDb;
            result.MaxFrequency = maxFreq;

            return result;
        }

        /// <summary>
        /// Reads in wave file and generates spectrogram
        /// </summary>
        private void SpectrogramFromWav()
        {
            int sampleId = 0;
            double[] buffer = new double[4096];
            while (_wavOpener.FilePosition < _wavOpener.FileSizeBytes)
            {
                if (sampleId == 4096)
                {
                    FFTResult result = PerformFFT(buffer);
                    spectrogramPane1.Windows.Add(result.FFT);
                    sampleId = 0;
                }

                buffer[sampleId] = (double)_wavOpener.GetNextSampleAs16Bit();
                sampleId++;
            }

            spectrogramPane1.UpdateGraph();

            // Set up trackbar
            trackBarWav.Maximum = _wavOpener.NumSamples;
            trackBarWav.Minimum = 0;
        }

        private void trackBarWav_ValueChanged(object sender, EventArgs e)
        {
            if (_wavOpener != null)
            {
                _wavOpener.SeekToAudioSample(trackBarWav.Value);
                double[] buffer = new double[4096];

                for (int i = 0; i < 4096; i++)
                {
                    buffer[i] = _wavOpener.GetNextSampleAs16Bit();
                }

                FFTResult result = PerformFFT(buffer);

                UpdateGraphs(buffer, result.FFT, true, (double)trackBarWav.Value / 44100d);
            }
        }

        private void spectrogramPane1_MouseMoveGraph(object sender, pHMb.Spectrogram.SpectrogramPane.MouseMoveGraphArgs e)
        {
            txtSpectrogramCoords.Text = String.Format("Spectrogram: {0:0.00} sec, {1:0.00} Hz, {2:00} dB", e.Time, e.Frequency, e.Amplitude);
        }

        private void buttonPlay_CheckStateChanged(object sender, EventArgs e)
        {
            if (_wavPlayThread != null)
            {
                _wavPlayThread.Abort();
            }

            if (buttonPlay.Checked)
            {
                _wavPlayThread = new Thread(new ParameterizedThreadStart(DoPlay));
                _wavPlayThread.Start(double.Parse(comboPlaySpeed.Text));
            }
        }

        public delegate bool TrackBarAdvanceDelegate(int value);

        public bool AdvanceTrackbar(int value)
        {
            if (this.InvokeRequired)
            {
                return (bool)this.Invoke(new TrackBarAdvanceDelegate(AdvanceTrackbar), value);
            }
            else
            {
                if (trackBarWav.Value + value > trackBarWav.Maximum)
                {
                    return false;
                }
                else
                {
                   
                    trackBarWav.Value += value;
                    return true;
                }
            }
        }

        private void DoPlay(object speed)
        {
            if (_wavOpener != null)
            {
                while (AdvanceTrackbar(4096))
                {
                    Thread.Sleep((int)(((4096d / 44100d) / (double)speed) * 1000));
                }
            }
        }

        public AForge.Math.Complex[] Hann(AForge.Math.Complex[] iwv)
        {
            int N = iwv.Length;

            for (int n = 1; n < N; n++)
                iwv[n].Re = iwv[n].Re * (0.54f - 0.46f * (float)Math.Cos((2 * Math.PI * n) / (N - 1))); 

            return iwv;
        }

        private void txtDevisor_TextChanged(object sender, EventArgs e)
        {
            int divisor;
            if (Int32.TryParse(txtDevisor.Text, out divisor))
            {
                spectrogramPane1.Divisor = divisor;
                spectrogramPane1.UpdateGraph();
            }
        }

        private void buttonFilter_CheckStateChanged(object sender, EventArgs e)
        {
            _enableFiltering = buttonFilter.Checked;
        }

        private void numAmplification_ValueChanged(object sender, EventArgs e)
        {
            _amplification = (double)numAmplification.Value;
        }

        private void txtCutOffPercent_TextChanged(object sender, EventArgs e)
        {
            int.TryParse(txtCutOffPercent.Text, out _limitPercent);
        }

        private void buttonShowGraphs_CheckedChanged(object sender, EventArgs e)
        {
            if (buttonShowGraphs.Checked)
            {
                splitContainer1.Panel1Collapsed = false;
                _processGraphs = true;
            }
            else
            {
                splitContainer1.Panel1Collapsed = true;
                _processGraphs = false;
            }
        }

        private void exportSpectToolStripMenuItem_Click(object sender, EventArgs e)
        {
            spectrogramPane1.ExportTextFile("export.csv");
        }
    }
}
